import BaseUtil from '@/BaseUtil'
import MathUtil from '@/MathUtil'
import RectUtil from '@/RectUtil'
import SelectUtil from '@/SelectUtil'
import VestCommon from '../Common/VestCommon'
import RandomUtil from '@/RandomUtil'
import AttributeUtil from '@/AttributeUtil'
import SelectCommon from './SelectCommon'
import ArrayUtil from '@/ArrayUtil'

declare global {
    interface TargetMissileConfig {
        missile: string
        missileUnit?: unit
        caster: unit
        target: unit
        speed: number
        radian?: number
        curX?: number
        curY?: number
        notDisroyOnEnd?: boolean
        onReach?: (p: TargetMissileConfig) => void
        onAbord?(p: TargetMissileConfig): void
        other?: any
    }
    interface 定点投射物参数<T = any> {
        tarX: number
        tarY: number
        startX?: number
        startY?: number
        speed: number
        radian: number
        missile: string
        caster?: unit
        missileUnit?: unit
        curX?: number
        curY?: number
        notDisroyOnEnd?: boolean
        onReachTarget?: (p: 定点投射物参数<T>) => void
        other?: T
    }
    interface 定点限高投射物参数<T = any> {
        tarX: number
        tarY: number
        speed?: number
        time?: number
        missile: string
        caster?: unit
        missileUnit?: unit
        curX?: number
        curY?: number
        maxHeight?: number
        notDisroyOnEnd?: boolean
        onReachTarget?: (p: 定点限高投射物参数<T>) => void
        other?: T
        fixFacing?: number
    }
    interface LineMissileConfig<T = any> {
        angle: number
        speed?: number
        time?: number
        distance: number
        radius?: number
        missile: string
        missileUnit?: unit
        is_through?: boolean
        curX?: number
        curY?: number
        caster: unit
        notDisroyOnEnd?: boolean
        onHit?: (p: LineMissileConfig<T>, hitBody: unit) => void
        onFinish?: (p: LineMissileConfig<T>) => void
        other?: T
        curLoop?: number
        notHit?: boolean
        tag?: 'enemy' | 'ally' | 'all'
    }
    interface HitFlyConfig<T = any> {
        target: unit
        caster: unit
        angle: number
        time?: number
        speed?: number
        distance: number
        maxHeight: number
        isStun?: boolean
        curX?: number
        curY?: number
        onBegin?: (p: HitFlyConfig<T>) => HitFlyConfig
        onInterval?: (p: HitFlyConfig<T>) => void
        onEnd?: (p: HitFlyConfig<T>) => void
        other?: T
        curLoop?: number
    }
    interface 制导投射物参数 {
        caster: unit
        tarX: number
        tarY: number
        speed: number
        missile: string
        missileUnit?: unit
        initAngle: number
        curX?: number
        curY?: number
        onReach?: (p: 制导投射物参数) => void
    }
    interface 曲线投射物参数 {
        caster: unit
        tarX: number
        tarY: number
        speed: number
        radius: number
        maxHeight: number
        missile?: string
        missileUnit?: unit
        curX?: number
        curY?: number
        side?: 1 | -1
        onEnd?: (p: 曲线投射物参数) => void
    }
}

function degTurn(ang: number) {
    return (ang + 360) % 360
}

export default class MovementCommon {
    static TargetMissile(p: TargetMissileConfig) {
        if (
            !IsUnitAliveBJ(p.target) ||
            GetUnitState(p.target, UNIT_STATE_LIFE) < 0.4
        ) {
            p.onAbord && p.onAbord(p)
            p.missileUnit && KillUnit(p.missileUnit)
            return p
        }
        if (!p.curX && !p.curY) {
            p.curX = GetUnitX(p.caster)
            p.curY = GetUnitY(p.caster)
        }
        let x = p.curX
        let y = p.curY
        let tx = GetUnitX(p.target)
        let ty = GetUnitY(p.target)
        let ag = MathUtil.angleBetweenCoords(x, y, tx, ty)

        p.missileUnit = VestCommon.取马甲并设置(x, y, ag)
        DzSetUnitModel(p.missileUnit, p.missile)
        let spd = p.speed / 25

        let initDis = MathUtil.distanceBetweenPoints(x, y, tx, ty)
        let h = initDis * (p.radian ?? 0)
        h = Math.max(60, h)
        BaseUtil.onTimer(0.04, (c) => {
            if (
                !IsUnitAliveBJ(p.target) ||
                GetUnitState(p.target, UNIT_STATE_LIFE) < 0.4
            ) {
                KillUnit(p.missileUnit)
                return false
            }
            let tx = GetUnitX(p.target)
            let ty = GetUnitY(p.target)
            let dis = MathUtil.distanceBetweenPoints(p.curX, p.curY, tx, ty)
            let ag = MathUtil.angleBetweenCoords(p.curX, p.curY, tx, ty)
            p.curX = p.curX + CosBJ(ag) * spd
            p.curY = p.curY + SinBJ(ag) * spd
            SetUnitX(p.missileUnit, p.curX)
            SetUnitY(p.missileUnit, p.curY)
            EXSetUnitFacing(p.missileUnit, ag)

            let count = MathUtil.distanceBetweenPoints(x, y, tx, ty) / spd
            let fh = Sin((c / count) * Math.PI) * h + 60
            fh = Math.max(60, fh)
            SetUnitFlyHeight(p.missileUnit, fh, c * 1000)
            let isReached = dis <= spd
            if (isReached) {
                if (!p.notDisroyOnEnd) {
                    // KillUnit(p.missileUnit);
                    VestCommon.returnVest(p.missileUnit)
                }
                p.onReach && p.onReach(p)
            }
            return !isReached
        })
        return p
    }
    static 定点投射物(p: 定点投射物参数) {
        if (p.caster && !p.startX && !p.startY) {
            p.startX = GetUnitX(p.caster)
            p.startY = GetUnitY(p.caster)
        }
        let ag = MathUtil.angleBetweenCoords(p.startX, p.startY, p.tarX, p.tarY)
        let dis = MathUtil.distanceBetweenPoints(
            p.startX,
            p.startY,
            p.tarX,
            p.tarY
        )
        let h = dis * p.radian
        if (!p.missileUnit) {
            // p.missileUnit = CreateUnit(GetOwningPlayer(p.caster), 'ewnv', p.startX, p.startY, GetUnitFacing(p.caster));
            p.missileUnit = VestCommon.取马甲并设置(
                p.startX,
                p.startY,
                GetUnitFacing(p.caster)
            )
            DzSetUnitModel(p.missileUnit, p.missile)
        }
        let count = (dis / p.speed) * 25
        p.curX = p.startX
        p.curY = p.startY
        let spd = dis / count
        BaseUtil.onTimer(0.04, (c) => {
            p.curX = p.curX + CosBJ(ag) * spd
            p.curY = p.curY + SinBJ(ag) * spd
            SetUnitX(p.missileUnit, p.curX)
            SetUnitY(p.missileUnit, p.curY)
            let fh = Sin((c / count) * Math.PI) * h
            fh = Math.max(60, fh)
            SetUnitFlyHeight(p.missileUnit, fh, c * 1000)
            if (c >= count) {
                p.onReachTarget && p.onReachTarget(p)
                if (!p.notDisroyOnEnd) {
                    // KillUnit(p.missileUnit);
                    VestCommon.returnVest(p.missileUnit)
                }
            }
            return c < count
        })
        return p
    }
    static 定点限高投射物(p: 定点限高投射物参数) {
        p.curX = GetUnitX(p.caster)
        p.curY = GetUnitY(p.caster)
        let f = MathUtil.angleBetweenCoords(p.curX, p.curY, p.tarX, p.tarY)
        p.missileUnit = VestCommon.取马甲并设置(
            p.curX,
            p.curY,
            p.fixFacing || f
        )
        DzSetUnitModel(p.missileUnit, p.missile)
        let dis = MathUtil.distanceBetweenPoints(p.curX, p.curY, p.tarX, p.tarY)
        let count = 0
        let spd = 0
        if (p.speed) {
            spd = p.speed / 25
            count = dis / spd
        } else {
            count = p.time * 25
            spd = dis / count
        }
        BaseUtil.onTimer(0.04, (c) => {
            p.curX += spd * CosBJ(f)
            p.curY += spd * SinBJ(f)
            SetUnitX(p.missileUnit, p.curX)
            SetUnitY(p.missileUnit, p.curY)
            let fh = Sin((c / count) * Math.PI) * p.maxHeight
            fh = Math.max(60, fh)
            SetUnitFlyHeight(p.missileUnit, fh, c * 1000)
            if (c >= count) {
                p.onReachTarget && p.onReachTarget(p)
                if (!p.notDisroyOnEnd) {
                    VestCommon.returnVest(p.missileUnit)
                }
            }
            return c < count
        })
        return p
    }
    static LineMissile(p: LineMissileConfig) {
        p.tag = p.tag || 'enemy'
        p.curX = GetUnitX(p.caster)
        p.curY = GetUnitY(p.caster)
        p.missileUnit = VestCommon.取马甲并设置(p.curX, p.curY, p.angle)
        DzSetUnitModel(p.missileUnit, p.missile)
        let count = 0
        let spd = 0
        if (p.speed) {
            spd = p.speed / 25
            count = p.distance / spd
        } else {
            count = p.time * 25
            spd = p.distance / count
        }
        BaseUtil.onTimer(0.04, (c) => {
            p.curX += spd * CosBJ(p.angle)
            p.curY += spd * SinBJ(p.angle)
            SetUnitX(p.missileUnit, p.curX)
            SetUnitY(p.missileUnit, p.curY)
            p.curLoop = c
            if (!p.notHit) {
                let es: unit[] = null
                if (p.tag == 'enemy') {
                    es = SelectUtil.getEnemyUnitsInRangeOrderByDistance(
                        p.caster,
                        p.radius,
                        p.curX,
                        p.curY
                    )
                } else if (p.tag == 'ally') {
                    es = SelectCommon.getAllyUnitInRange(
                        p.caster,
                        p.radius,
                        p.curX,
                        p.curY
                    )
                } else {
                    es = [].concat(
                        (es = SelectUtil.getEnemyUnitsInRangeOrderByDistance(
                            p.caster,
                            p.radius,
                            p.curX,
                            p.curY
                        )),
                        SelectCommon.getAllyUnitInRange(
                            p.caster,
                            p.radius,
                            p.curX,
                            p.curY
                        )
                    )
                }
                if (es.includes(p.caster)) {
                    ArrayUtil.removeElement(es, p.caster)
                }
                if (es.length > 0) {
                    if (p.is_through) {
                        if (p.onHit) {
                            for (let item of es) {
                                p.onHit(p, item)
                            }
                        }
                    } else {
                        if (p.onHit) p.onHit(p, es[0])
                        // KillUnit(p.missileUnit);
                        VestCommon.returnVest(p.missileUnit)
                        if (p.onFinish) p.onFinish(p)
                        return false
                    }
                }
            }
            if (c >= count) {
                if (!p.notDisroyOnEnd) {
                    // KillUnit(p.missileUnit);
                    VestCommon.returnVest(p.missileUnit)
                }
                if (p.onFinish) p.onFinish(p)
            }
            return c < count
        })
        return p
    }
    static hitFly(p: HitFlyConfig) {
        p.curX = GetUnitX(p.target)
        p.curY = GetUnitY(p.target)
        UnitAddAbility(p.target, 'Arav')
        UnitRemoveAbility(p.target, 'Arav')
        let count = 0
        let spd = 0
        if (p.speed) {
            spd = p.speed / 25
            count = p.distance / spd
        } else {
            count = p.time * 25
            spd = p.distance / count
        }
        BaseUtil.onTimer(0.04, (c) => {
            p.curX += spd * CosBJ(p.angle)
            p.curY += spd * SinBJ(p.angle)

            if (RectUtil.isInPlayableArea(p.curX, p.curY)) {
                if (p.isStun) {
                    SetUnitPosition(p.target, p.curX, p.curY)
                } else {
                    SetUnitX(p.target, p.curX)
                    SetUnitY(p.target, p.curY)
                }
            }
            if (p.maxHeight) {
                let fh = Sin((c / count) * Math.PI) * p.maxHeight
                SetUnitFlyHeight(p.target, fh, c * 1000)
            }
            p.curLoop = c
            if (p.onInterval) {
                p.onInterval(p)
            }
            if (c >= count) {
                if (p.onEnd) p.onEnd(p)
            }
            return c < count
        })
        return p
    }
    static 制导投射物(p: 制导投射物参数) {
        if (!p.missileUnit) {
            p.missileUnit = VestCommon.取马甲并设置(p.curX, p.curY, p.initAngle)
            DzSetUnitModel(p.missileUnit, p.missile)
        } else {
            p.curX = GetUnitX(p.missileUnit)
            p.curY = GetUnitY(p.missileUnit)
        }
        let spd = p.speed / 25
        // let spdStep = p.speed / 80;
        // let max = p.speed / 10;
        let ta = MathUtil.angleBetweenCoords(p.curX, p.curY, p.tarX, p.tarY)
        let t1 = ta + 90
        let t2 = ta - 90
        let ab1 = Math.abs(degTurn(t1) - degTurn(p.initAngle))
        let ab2 = Math.abs(degTurn(t2) - degTurn(p.initAngle))
        let angSpd = ab1 < ab2 ? -5 : 5

        BaseUtil.onTimer(0.04, (c) => {
            let dis = MathUtil.distanceBetweenPoints(
                p.curX,
                p.curY,
                p.tarX,
                p.tarY
            )
            let tarAngle = MathUtil.angleBetweenCoords(
                p.curX,
                p.curY,
                p.tarX,
                p.tarY
            )
            if (Math.abs(tarAngle - p.initAngle) % 360 <= 5) {
                p.initAngle = tarAngle
            } else {
                p.initAngle += angSpd
            }
            // spd = MathUtil.clamp(spd + spdStep, 0, max);
            p.curX += spd * CosBJ(p.initAngle)
            p.curY += spd * SinBJ(p.initAngle)
            SetUnitX(p.missileUnit, p.curX)
            SetUnitY(p.missileUnit, p.curY)
            EXSetUnitFacing(p.missileUnit, p.initAngle)
            let isReach = dis <= spd
            if (isReach) {
                VestCommon.returnVest(p.missileUnit)
                p.onReach && p.onReach(p)
            }
            return !isReach
        })
    }
    static Sin曲线投射物(p: 曲线投射物参数) {
        let x = GetUnitX(p.caster)
        let y = GetUnitY(p.caster)
        p.side = p.side || RandomUtil.randomBool() ? 1 : -1
        let distance = MathUtil.distanceBetweenPoints(x, y, p.tarX, p.tarY)
        let count = (distance / p.speed) * 25
        let angle = MathUtil.angleBetweenCoords(x, y, p.tarX, p.tarY)
        let spd = distance / count
        if (p.missileUnit == null) {
            p.missileUnit = VestCommon.取马甲并设置(x, y, angle)
            DzSetUnitModel(p.missileUnit, p.missile)
        }
        BaseUtil.onTimer(0.04, (c) => {
            x += CosBJ(angle) * spd
            y += SinBJ(angle) * spd
            let per = Sin((c / count) * Math.PI)
            let h = per * p.radius
            let { x: cx, y: cy } = MathUtil.polarProjection(
                x,
                y,
                h,
                angle + p.side * 90
            )
            p.curX = cx
            p.curY = cy
            SetUnitX(p.missileUnit, cx)
            SetUnitY(p.missileUnit, cy)
            SetUnitFlyHeight(p.missileUnit, per * p.maxHeight, 2000)
            if (c >= count) {
                if (p.onEnd) p.onEnd(p)
                VestCommon.returnVest(p.missileUnit)
                return false
            }
            return c < count
        })
    }
    static dropItemFromUnit(id: number | string, u: unit, attr?: AppAttribute) {
        let { x, y } = MathUtil.polarProjection(
            GetUnitX(u),
            GetUnitY(u),
            RandomUtil.nextReal(50, 200),
            RandomUtil.randomAngle()
        )
        MovementCommon.定点限高投射物({
            missile:
                'Objects\\InventoryItems\\TreasureChest\\treasurechest.mdl',
            caster: u,
            time: 0.5,
            maxHeight: 200,
            tarX: x,
            tarY: y,
            other: { id, attr },
            onReachTarget: MovementCommon.createItem,
            fixFacing: -90,
        })
    }
    private static createItem(p: 定点限高投射物参数) {
        let item = CreateItem(p.other.id, p.curX, p.curY)
        let type = GetItemType(item)
        if (type != ITEM_TYPE_CHARGED && item) {
            p.other.attr && AttributeUtil.setItemAttribute(item, p.other.attr)
        }
    }
}
